/*
	File:		TSRollingLog.cpp

	Contains:	Implements object defined in .h file


*/
#define WIN32_LEAN_AND_MEAN
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>   
#include <sys/stat.h>
#include <errno.h> 
#ifndef WIN32
#include <sys/time.h>
#endif
#include "TSRollingLog.h"
#include "OS.h"
#include "OSMemory.h"
#include "OSArrayObjectDeleter.h"
 
TSRollingLog::TSRollingLog() : 	
	fLog(NULL), 
	fLogCreateTime(-1),
	fLogFullPath(NULL)
{
	
}

TSRollingLog::~TSRollingLog()
{
	//
	// Log should already be closed, but just in case...
	this->CloseLog();
	delete [] fLogFullPath;
}

void TSRollingLog::WriteToLog(char* inLogData, Bool16 allowLogToRoll)
{
	OSMutexLocker locker(&fMutex);
	
	if (allowLogToRoll)
		(void)this->CheckRollLog();
	if (fLog != NULL)
	{
		char theDateBuffer[TSRollingLog::kMaxDateBufferSizeInBytes];
		Bool16 result = TSRollingLog::FormatDate(theDateBuffer, false);

		::fprintf(fLog, "%s %s\n", theDateBuffer, inLogData);
		static int s_write2filelimit = 0;
		//noted by suijz on 2013-4-24
		//if(++s_write2filelimit > 100)//yb added , decrease the IO operation.
        {
            ::fflush(fLog);
            s_write2filelimit = 0;
        }
	}
}

Bool16 TSRollingLog::RollLog()
{
	OSMutexLocker locker(&fMutex);

	//returns false if an error occurred, true otherwise

	//close the old file.
	this->CloseLog();
	//rename the old file
	Bool16 result = this->RenameLogFile(fLogFullPath);
	if (result)
		this->EnableLog();//re-opens log file

	return result;
}


void TSRollingLog::EnableLog( Bool16 appendDotLog )
{
	//
	// Start this object running!
	this->Signal(Task::kStartEvent);

	OSMutexLocker locker(&fMutex);

	//kill off the cached value
	if (fLogFullPath != NULL)
	{
		delete[] fLogFullPath;
		fLogFullPath = NULL;
	}
	
	//The log name passed into this function is a private copy of the path,
	//we are responsible for it.
	char *logDirectory = this->GetLogDir();
	char *logBaseName = this->GetLogName();
	if(logDirectory == NULL || logBaseName == NULL )
	{
	    return;
	}
	
	//- don't append .log for playlist broadcaster 
	int pathLen = ::strlen(logDirectory) + ::strlen(logBaseName) + 2;
	
	if ( appendDotLog )
		pathLen +=+ ::strlen(".log");
		
	fLogFullPath = NEW char[pathLen];
	
	//copy over the directory - append a '/' if it's missing
	::strcpy(fLogFullPath, logDirectory);
	if (fLogFullPath[::strlen(fLogFullPath)-1] != kPathDelimiterChar)
	{
		::strcat(fLogFullPath, kPathDelimiterString);
	}
	
	//copy over the base filename & suffix
	::strcat(fLogFullPath, logBaseName);
	
	//- don't append .log for playlist broadcaster 
	if ( appendDotLog )
		::strcat(fLogFullPath, ".log");
	
	//we need to make sure that when we create a new log file, we write the
	//log header at the top
	Bool16 logExists = DoesFileExist(fLogFullPath);
	
	//create the log directory if it doesn't already exist
	if (!logExists)
		OS::RecursiveMakeDir(logDirectory);

	
	fLog = fopen(fLogFullPath, "a+");//open for "append"

	if (NULL != fLog)
	{
		//If the file is new, write a log header with the create time of the file.
		//If it's old, read the log header to find the create time of the file.
		if (!logExists)
			fLogCreateTime = this->WriteLogHeader(fLog);
		else
			fLogCreateTime = this->ReadLogHeader(fLog);
		if(fLogCreateTime < 0)
			fLogCreateTime = time(NULL);
	}
}

void TSRollingLog::CloseLog()
{
	OSMutexLocker locker(&fMutex);

	if (fLog != NULL)
	{
		::fclose(fLog);
		fLog = NULL;
	}
}

//returns false if some error has occurred
Bool16 TSRollingLog::FormatDate(char *ioDateBuffer, Bool16 logTimeInGMT)
{
	Assert(NULL != ioDateBuffer);
	
#ifdef WIN32
	SYSTEMTIME timeInfo;
	GetLocalTime(&timeInfo);
	sprintf(ioDateBuffer, "%04u-%02u-%02u %02u:%02u:%02u.%03u",
		timeInfo.wYear, timeInfo.wMonth, timeInfo.wDay,
		timeInfo.wHour, timeInfo.wMinute, timeInfo.wSecond,
		timeInfo.wMilliseconds);
#else
	timeval now;
	gettimeofday(&now, NULL);
	tm *timeInfo = localtime(&now.tv_sec);
	sprintf(ioDateBuffer, "%02u-%02u %02u:%02u:%02u.%03u",
		timeInfo->tm_mon + 1, timeInfo->tm_mday,
		timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec, 
		(uint32_t) now.tv_usec / 1000);
#endif

	return true;
	//use ansi routines for getting the date.
	time_t calendarTime = ::time(NULL);
	Assert(-1 != calendarTime);
	if (-1 == calendarTime)
		return false;

#ifdef WIN32	
	struct tm* theTime = NULL;
#else	
	struct tm theTime;
#endif	
	if (logTimeInGMT)
	{
#ifdef WIN32	
		theTime = ::gmtime(&calendarTime);
#else
		gmtime_r(&calendarTime,&theTime);
#endif		
	}
	else
	{
#ifdef WIN32	
		theTime = ::localtime(&calendarTime);
#else
		localtime_r(&calendarTime,&theTime);
#endif		
	}
	
	//Assert(NULL != theTime);
	
	//if (NULL == theTime)
	//	return false;
		
	// date time needs to look like this for extended log file format: 2001-03-16 23:34:54
	// this wonderful ANSI routine just does it for you.
	// the format is YYYY-MM-DD HH:MM:SS
	// the date time is in GMT, unless logTimeInGMT is false, in which case
	// the time logged is local time
	//::strftime(ioDateBuffer, kMaxDateBufferSize, "%d/%b/%Y:%H:%M:%S", theLocalTime);
#ifdef WIN32	
	::strftime(ioDateBuffer, kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", theTime);	
#else
	::strftime(ioDateBuffer, kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", &theTime);	
#endif		
	return true;
}

Bool16 TSRollingLog::CheckRollLog()
{
	//returns false if an error occurred, true otherwise
	if (fLog == NULL)
		return true;
	
	//first check to see if log rolling should happen because of a date interval.
	//This takes precedence over size based log rolling
	// this is only if a client connects just between 00:00:00 and 00:01:00
	// since the task object runs every minute
	
	// when an entry is written to the log file, only the file size must be checked
	// to see if it exceeded the limits
	
	// the roll interval should be monitored in a task object 
	// and rolled at midnight if the creation time has exceeded.
	if ((-1 != fLogCreateTime) && (0 != this->GetRollIntervalInDays()))
	{	
		time_t logCreateTimeMidnight = -1;
		TSRollingLog::ResetToMidnight(&fLogCreateTime, &logCreateTimeMidnight);
		Assert(logCreateTimeMidnight != -1);
		
		time_t calendarTime = ::time(NULL);

		Assert(-1 != calendarTime);
		if (-1 != calendarTime)
		{
			double theExactInterval = ::difftime(calendarTime, logCreateTimeMidnight);
			SInt32 theCurInterval = (SInt32)::floor(theExactInterval);
			
			//transfer_roll_interval is in days, theCurInterval is in seconds
			SInt32 theRollInterval = this->GetRollIntervalInDays() * 60 * 60 * 24;
			if (theCurInterval > theRollInterval)
				return this->RollLog();
		}
	}
	
	
	//now check size based log rolling
	UInt32 theCurrentPos = ::ftell(fLog);
	//max_transfer_log_size being 0 is a signal to ignore the setting.
	if ((this->GetMaxLogBytes() != 0) &&
		(theCurrentPos > this->GetMaxLogBytes()))
		return this->RollLog();
	return true;
}

Bool16 TSRollingLog::RenameLogFile(const char* inFileName)
{
	//returns false if an error occurred, true otherwise

	//this function takes care of renaming a log file from "myLogFile.log" to
	//"myLogFile.981217000.log" or if that is already taken, myLogFile.981217001.log", etc 
	
	//fix 2287086. Rolled log name can be different than original log name
	char *logDirectory = this->GetLogDir();
	char *logBaseName = this->GetLogName();
	if(logDirectory == NULL || logBaseName == NULL )
	{
	    return false;
	}	
	//create the log directory if it doesn't already exist
	OS::RecursiveMakeDir(logDirectory);
	
		
	//QTStreamingServer.981217003.log
	//format the new file name

	//20081128 modify by tangdaoyong
	int ntheNewNameBufferLen = ::strlen(logDirectory) + strlen(logBaseName) + 10 + 1 + 20/*kMaxFilenameLengthInBytes*/ + 3;
	char * theNewNameBuffer = new char[ntheNewNameBufferLen];
	if(theNewNameBuffer == NULL)
	{
	    return false;
	}	
	memset(theNewNameBuffer, 0, ntheNewNameBufferLen);

	//copy over the directory - append a '/' if it's missing
	::strcpy(theNewNameBuffer, logDirectory);
	if (theNewNameBuffer[::strlen(theNewNameBuffer)-1] != kPathDelimiterChar)
	{
		::strcat(theNewNameBuffer, kPathDelimiterString);
	}
	
	//copy over the base filename
	::strcat(theNewNameBuffer, logBaseName);

	//append the date the file was created
	struct tm* theLocalTime = ::localtime(&fLogCreateTime);
	char timeString[10];
	::strftime(timeString,  10, ".%y%m%d", theLocalTime);
	::strcat(theNewNameBuffer, timeString);
	
	SInt32 theBaseNameLength = ::strlen(theNewNameBuffer);


	//loop until we find a unique name to rename this file
	//and append the log number and suffix
	SInt32 theErr = 0;
	for (SInt32 x = 0; (theErr == 0) && (x<=1000); x++)
	{
		if (x  == 1000)	//we don't have any digits left, so just reuse the "---" until tomorrow...
		{
			//add a bogus log number and exit the loop
			sprintf(theNewNameBuffer + theBaseNameLength, "---.log");
			break;
		}

		//add the log number & suffix
		sprintf(theNewNameBuffer + theBaseNameLength, "%03ld.log", x);

		//assume that when ::stat returns an error, it is becase
		//the file doesnt exist. Once that happens, we have a unique name
		// csl - shouldn't you watch for a ENOENT result?
		struct stat theIdontCare;
     	theErr = ::stat(theNewNameBuffer, &theIdontCare);
		WarnV((theErr == 0 || OSThread::GetErrno() == ENOENT), "unexpected stat error in RenameLogFile");
		
	}
	
	//rename the file. Use posix rename function
	int result = ::rename(inFileName, theNewNameBuffer);
	if (result == -1)
		theErr = (SInt32)OSThread::GetErrno();
	else
		theErr = 0;
		
	WarnV(theErr == 0 , "unexpected rename error in RenameLogFile");
	
	delete []theNewNameBuffer;

	if (theErr != 0)
		return false;
	else
		return true;	
}

Bool16 TSRollingLog::DoesFileExist(const char *inPath)
{
	struct stat theStat;
	int theErr = ::stat(inPath, &theStat);
	if (theErr != 0)
		return false;
	else
		return true;
}

time_t TSRollingLog::WriteLogHeader(FILE* inFile)
{
	OSMutexLocker locker(&fMutex);

	//The point of this header is to record the exact time the log file was created,
	//in a format that is easy to parse through whenever we open the file again.
	//This is necessary to support log rolling based on a time interval, and POSIX doesn't
	//support a create date in files.
	time_t calendarTime = ::time(NULL);
	Assert(-1 != calendarTime);
	if (-1 == calendarTime)
		return -1;

	struct tm* theLocalTime = ::localtime(&calendarTime);
	Assert(NULL != theLocalTime);
	if (NULL == theLocalTime)
		return -1;
	
	//
	// Files are always created at hour 0 (we don't care about the time, we always
	// want them to roll at midnight.
	//theLocalTime->tm_hour = 0;
	//theLocalTime->tm_min = 0;
	//theLocalTime->tm_sec = 0;

	char tempbuf[1024];
	::strftime(tempbuf, sizeof(tempbuf), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime);
	//::sprintf(tempbuf, "#Log File Created On: %d/%d/%d %d:%d:%d %d:%d:%d GMT\n",
	//			theLocalTime->tm_mon, theLocalTime->tm_mday, theLocalTime->tm_year,
	//			theLocalTime->tm_hour, theLocalTime->tm_min, theLocalTime->tm_sec,
	//			theLocalTime->tm_yday, theLocalTime->tm_wday, theLocalTime->tm_isdst);
	this->WriteToLog(tempbuf, !kAllowLogToRoll);
	
	return this->ReadLogHeader(inFile);
}

time_t TSRollingLog::ReadLogHeader(FILE* inFile)
{
	OSMutexLocker locker(&fMutex);

	//This function reads the header in a log file, returning the time stored
	//at the beginning of this file. This value is used to determine when to
	//roll the log.
	//Returns -1 if the header is bogus. In that case, just ignore time based log rolling

	//first seek to the beginning of the file
	SInt32 theCurrentPos = ::ftell(inFile);
	if (theCurrentPos == -1)
		return -1;
	(void)::rewind(inFile);

	const UInt32 kMaxHeaderLength = 500;
	char theFirstLine[kMaxHeaderLength];
	
	if (NULL == ::fgets(theFirstLine, kMaxHeaderLength, inFile))
	{
		::fseek(inFile, 0, SEEK_END);
		return -1;
	}
	::fseek(inFile, 0, SEEK_END);
	
	struct tm theFileCreateTime;
	
	// Zero out fields we will not be using
	theFileCreateTime.tm_isdst = -1;
	theFileCreateTime.tm_wday = 0;
	theFileCreateTime.tm_yday = 0;
	
	//if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n",
	//			&theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
	//			&theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
	//	return -1;
	
	//
	// We always want to roll at hour 0, so ignore the time of creation
	char *pTime = strstr(theFirstLine, "#Log File Created On");
	if(pTime == NULL)
	{
		return -1;
	}

	if (EOF == ::sscanf(pTime, "#Log File Created On: %d/%d/%d %d:%d:%d\n", 
				&theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
				&theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
		return -1;

	//
	// It should be like this anyway, but if the log file is legacy, then...
	// No! The log file will have the actual time in it but we shall return the exact time
	//theFileCreateTime.tm_hour = 0;
	//theFileCreateTime.tm_min = 0;
	//theFileCreateTime.tm_sec = 0;
	
	// Actually, it seems like all platforms need this.
//#ifdef __Win32__
	// Win32 has slightly different atime basis than UNIX.
	theFileCreateTime.tm_yday--;
	theFileCreateTime.tm_mon--;
	theFileCreateTime.tm_year -= 1900;
//#endif

#if 0
	//use ansi routines for getting the date.
	time_t calendarTime = ::time(NULL);
	Assert(-1 != calendarTime);
	if (-1 == calendarTime)
		return false;
		
	struct tm* theLocalTime = ::localtime(&calendarTime);
	Assert(NULL != theLocalTime);
	if (NULL == theLocalTime)
		return false;
#endif

	//ok, we should have a filled in tm struct. Convert it to a time_t.
	//time_t thePoopTime = ::mktime(theLocalTime);
	time_t theTime = ::mktime(&theFileCreateTime);
	return theTime;
}

SInt64 TSRollingLog::Run()
{
	//
	// If we are going away, just return
	EventFlags events = this->GetEvents();
	if (events & Task::kKillEvent)
		return -1;
	
	OSMutexLocker locker(&fMutex);
	
	UInt32 theRollInterval = (this->GetRollIntervalInDays())  * 60 * 60 * 24;
	
	if((fLogCreateTime != -1) && (fLog != NULL))
	{
		time_t logRollTimeMidnight = -1;
		this->ResetToMidnight(&fLogCreateTime, &logRollTimeMidnight);
		Assert(logRollTimeMidnight != -1);
		
		if(theRollInterval != 0)
		{
			time_t calendarTime = ::time(NULL);
			Assert(-1 != calendarTime);
			double theExactInterval = ::difftime(calendarTime, logRollTimeMidnight);
			if(theExactInterval > 0) {
				UInt32 theCurInterval = (UInt32)::floor(theExactInterval);
				if (theCurInterval >= theRollInterval)
					this->RollLog();
			}
		}
	}
	return 60 * 1000;
}

void TSRollingLog::ResetToMidnight(time_t* inTimePtr, time_t* outTimePtr) 
{
	if(*inTimePtr == -1)
	{
		*outTimePtr = -1;
		return;
	}
	
	struct tm* theLocalTime = ::localtime(inTimePtr);
	Assert(theLocalTime != NULL);

	theLocalTime->tm_hour = 0;
	theLocalTime->tm_min = 0;
	theLocalTime->tm_sec = 0;
	
	// some weird stuff
	//theLocalTime->tm_yday--;
	//theLocalTime->tm_mon--;
	//theLocalTime->tm_year -= 1900;

	*outTimePtr = ::mktime(theLocalTime);

}
